﻿using System;
using System.Web.Mvc;
using System.Web.Routing;
using cludg.Repository.Security;
using cludg.Services.Images;
using cludg.Services.Users;
using cludg.Web.Core.Menu;
using cludg.Web.Core.Security;
using cludg.Web.Core.Validation;
using cludg.Services.Security;
using cludg.Domain.Users;
using cludg.Infrastructure.Mailer;
using cludg.Web.Core.Controllers;
using cludg.Web.Core.ViewModels;
using cludg.Infrastructure.Core.Attributes;
using cludg.Web.Core.Utils;
using cludg.Web.ViewModels;

namespace cludg.Web.Controllers
{
	public class AccountController : BaseController
	{
		[Dependency]
		public IAdministration Administration { get; set; }
		[Dependency]
		public IUserService UserService { get; set; }
		// TODO: отправка писем должна быть в отдельном cron-процессе.
		[Dependency]
		public IMailer Mailer { get; set; }
		[Dependency]
		public ISecurityRepository SecurityRepository { get; set; }

		private readonly MenuDefinition _editMenuDefinition;


		public AccountController()
		{
			_editMenuDefinition = new MenuDefinition();
			_editMenuDefinition.Items.AddRange(
				new[] {
					new MenuItemDefinition
					{
						Caption = "Персональная информация",
						ControllerName = "Account",
						ActionName = "EditPersonalInfo",
						PermissionName = ""
					},
					new MenuItemDefinition
					{
						Caption = "Аккаунт",
						ControllerName = "Account",
						ActionName = "EditAccount",
						PermissionName = ""
					}
				});

		}

		#region Work with backUrl fot login

		protected const string BackUrlSessionVar = "LoginBackUrl";

		protected string LoginBackUrl
		{
			get
			{
				string backUrl = string.Empty;
				if (Session["LoginBackUrl"] != null)
				{
					backUrl = (string)Session["LoginBackUrl"];
				}
				return backUrl;
			}
			set
			{
				Session[BackUrlSessionVar] = value;
			}
		}

		#endregion

		public ActionResult Login()
		{
			if (Request.UrlReferrer != null)
				LoginBackUrl = Request.UrlReferrer.ToString();
			var model = CreateModel<LoginModel>();
			return View("Login", model);
		}

		[HttpPost]
		public ActionResult Login(LoginModel model)
		{
			if (ModelState.IsValid)
			{
				string userId;

				if (Membership.TryValidateUser(
					new Services.Security.Principals.LoginPassword
						{
							Login = model.Login,
							Password = model.Password
						},
					out userId))
				{
					Authehtication.SignIn(userId, model.Remember);
					// go to back url if set
					if (!string.IsNullOrEmpty(LoginBackUrl))
					{
						return Redirect(LoginBackUrl);
					}
					return RedirectToAction("Index", "Home");
				}
				ModelState.AddModelError(string.Empty, "Имя пользователя или пароль указаны неверно");
			}

			InitModel(model);
			return View("Login", model);
		}

		private static string GetUserDisplayName(LoginzaUtils.LoginzaUserInfo info)
		{
			if (!String.IsNullOrEmpty(info.Nickname))
				return info.Nickname;

			if (!String.IsNullOrEmpty(info.FullName))
				return info.FullName;

			if (!String.IsNullOrEmpty(info.FirstName) || !String.IsNullOrEmpty(info.LastName))
				return (info.FirstName + " " + info.LastName).Trim();

			if (!String.IsNullOrEmpty(info.Email))
				return info.Email;

			return info.Identity;
		}

		[HttpPost]
		public ActionResult LoginThirdParty(string token)
		{
			LoginzaUtils.LoginzaUserInfo info;
			LoginzaUtils.LoginzaError e;

			BaseModel model;
			if (!LoginzaUtils.TryRequestUserInfo(token, out info, out e))
			{
				model = CreateModel<LoginModel>();
				ModelState.AddModelError(string.Empty, e.Message);
				return View("Login", model);
			}

			string userId;
			ValidateUserError error;

			if (Membership.TryValidateUser(
				new Services.Security.Principals.ThirdParty { Ident = info.Identity },
				out userId, out error))
			{
				Authehtication.SignIn(userId, true);
				// go to back url if set
				if (!string.IsNullOrEmpty(LoginBackUrl))
				{
					return Redirect(LoginBackUrl);
				}
				return RedirectToAction("Index", "Home");
			}
			User user = new User
							{
								DisplayName = GetUserDisplayName(info),
								Email = info.Email
							};

			RegisterUserError regError;

			if (Administration.TryRegisterThirdPartyUser(user, info.Identity, out userId, out regError))
			{
				if (!String.IsNullOrEmpty(info.Photo))
					using (var photoStream = RequestUtils.Download(info.Photo))
					{
						if (photoStream != null)
							UserService.ChangeUserAvatar(userId, photoStream);
					}

				// Administration.ChangeUserAvatar();
				Authehtication.SignIn(userId, true);
				// go to back url if set
				if (!string.IsNullOrEmpty(LoginBackUrl))
				{
					return Redirect(LoginBackUrl);
				}
				return RedirectToAction("Index", "Home");
			}
			model = CreateModel<LoginModel>();
			ModelState.AddModelError(string.Empty, regError.Message);
			return View("Login", model);
		}

		public ActionResult Logout()
		{
			Authehtication.SignOut();

			return RedirectToAction("Index", "Home");
		}

		public ActionResult Register()
		{
			return View("Register", CreateModel<RegisterModel>());
		}

		[HttpPost]
		[Core.Captcha.CaptchaValidator]
		public ActionResult Register(RegisterModel model)
		{
			InitModel(model);

			if (ModelState.IsValid)
			{
				User user = new User { DisplayName = model.Name, Email = model.Email };

				string userId;
				RegisterUserError regError;

				if (Administration.TryRegisterUser(user, model.Name, model.Password, true, out userId, out regError))
				{
					string activationLink = UrlHelper.GenerateUrl(null,
						"Activate", "Account",
						null, Request.Url.Host, null,
						null, RouteTable.Routes, Request.RequestContext, true) + "/" + userId;
					// TODO: Параметры писем в настройки. Наверное, вообще нужен отдельный механизм получения этих настроек для разных типов писем
					Mailer.Send("", "admin@cludg.ru", user.Email, "Активация!", String.Format("Ссылка для активации: {0}", activationLink));

					return View("Registered", model);
				}
				ModelState.AddModelError(string.Empty, regError.Message);
			}

			return View("Register", model);
		}

		public ActionResult Activate(string id)
		{
			User user = Membership.GetUser(id);

			if (user != null)
			{
				Administration.ActivateUser(user);
				Authehtication.SignIn(user.Id, true);
			}
			// TODO: Наверное, надо отправлять пользователя на более информативную страницу
			return RedirectToAction("Index", "Home");
		}

		public ActionResult ResetPassword()
		{
			var model = CreateModel<ResetPasswordModel>();
			return View("ResetPassword", model);
		}

		[HttpPost]
		public ActionResult ResetPassword(ResetPasswordModel model)
		{
			InitModel(model);

			string newPassword;
			Administration.ResetUserPasswordByEmail(model.Email, out newPassword);
			if (!String.IsNullOrEmpty(newPassword))
			{
				// TODO:
				Mailer.Send(string.Empty, "admin@cludg.ru", model.Email, "Новый пароль!", String.Format("Новый пароль: {0}", newPassword));
			}

			return View("PasswordReseted", model);
		}

		[PermissionRequierd("EditAccount")]
		public ActionResult EditAccount()
		{
			var currentUser = GetCurrentUser();
			var secInfo = SecurityRepository.GetUserSecurityInfo(currentUser.Id);

			// Init model
			var model = CreateModel<AccountModel>();
			model.Email = currentUser.Email;
			InitEditUserMenu(model.EditUserMenu, "EditAccount");

			// Check for thir party login
			if (secInfo.LoginType == UserSecurityInfo.UserLoginType.ThirdParty)
			{
				ShowMessage(MessageType.Warning, "Редактирование данных невозможно, так как вы вошли по OpenID");
				model.NotEditable = true;
			}

			return View(model);
		}

		[HttpPost]
		[PermissionRequierd("EditAccount")]
		public ActionResult EditAccount(AccountModel model)
		{
			var currentUser = GetCurrentUser();
			var secInfo = SecurityRepository.GetUserSecurityInfo(currentUser.Id);

			InitModel(model);
			InitEditUserMenu(model.EditUserMenu, "EditAccount");

			// save account changes only for non third party accounts
			if (ModelState.IsValid && secInfo.LoginType != UserSecurityInfo.UserLoginType.ThirdParty)
			{
				// check current pass
				string userId;
				ValidateUserError error;

				if (!Membership.TryValidateUser(
					new Services.Security.Principals.LoginPassword { Login = secInfo.Name, Password = model.CurrentPassword },
					out userId, out error))
				{
					ModelState.AddModelError(string.Empty, error.Message);
				}
				else
				{
					// update and save user data
					currentUser.Email = model.Email;
					UserService.UpdateUser(currentUser);

					if (!string.IsNullOrEmpty(model.NewPassword))
					{
						Administration.SetUserPassword(currentUser.Id, model.NewPassword);
					}

					ShowMessage(MessageType.Success, "Изменения успешно сохранены");
				}
			}

			return View(model);
		}

		[PermissionRequierd("EditAccount")]
		public ActionResult EditPersonalInfo()
		{
			var model = CreateModel<PersonalInfoModel>();
			var currentUser = GetCurrentUser();

			model.Avatar = currentUser.Avatar;
			model.DateOfBirth = currentUser.DateOfBirth;
			model.Location = currentUser.Location;
			model.DisplayName = currentUser.DisplayName;
			InitEditUserMenu(model.EditUserMenu, "EditPersonalInfo");

			return View(model);
		}

		[HttpPost]
		[PermissionRequierd("EditAccount")]
		public ActionResult EditPersonalInfo(PersonalInfoModel model)
		{
			InitModel(model);
			InitEditUserMenu(model.EditUserMenu, "EditPersonalInfo");

			var currentUser = GetCurrentUser();
			model.Avatar = currentUser.Avatar;
			bool imageSaveSuccess = true;

			if (ModelState.IsValid)
			{
				// update and save user data
				currentUser.DisplayName = model.DisplayName;
				currentUser.Location = model.Location;
				currentUser.DateOfBirth = model.DateOfBirth;

				// clean avatar
				if (model.DeleteAvatar)
				{
					// TODO: Удалять картинки из репозитория?
					if (!string.IsNullOrEmpty(currentUser.SmallAvatar))
						ImageService.Delete(currentUser.SmallAvatar);
					if (!string.IsNullOrEmpty(currentUser.Avatar))
						ImageService.Delete(currentUser.Avatar);
					currentUser.SmallAvatar = string.Empty;
					currentUser.Avatar = string.Empty;
					model.Avatar = string.Empty;
					model.DeleteAvatar = false;
				}

				// avatar uploading
				if (Request.Files.Count > 0)
				{
					var avatarFile = Request.Files[model.AvatarUploadFileName];
					if (avatarFile != null && avatarFile.InputStream != null && avatarFile.ContentLength > 0)
					{
						// check for image size and type
						string validationMessage;
						if (!ImageValidationUtil.ValidatePostedImageFile(avatarFile,
							ImageValidationUtil.AvatarImageSize, out validationMessage))
						{
							imageSaveSuccess = false;
							ModelState.AddModelError(string.Empty, validationMessage);
						}
						else
						{
							var ids = ImageService.Add(avatarFile.InputStream, new[] {ImageSize.SmallAvatar, ImageSize.Avatar});
							string smallAvatarId = ids[0];
							string avatarId = ids[1];

							currentUser.SmallAvatar = smallAvatarId;
							currentUser.Avatar = avatarId;
							model.Avatar = avatarId;
						}
					}
				}

				if (imageSaveSuccess)
				{
					UserService.UpdateUser(currentUser);
					ShowMessage(MessageType.Success, "Изменения успешно сохранены");
				}
			}

			return View(model);
		}

		[PermissionRequierd("ViewProfile")]
		public ActionResult Profile(string id)
		{
			var model = CreateModel<ProfileModel>();
			model.User = Membership.GetUser(id);

			return View(model);
		}

		[PermissionRequierd("ViewProfile")]
		public ActionResult ProfileByName(string name)
		{
			var model = CreateModel<ProfileModel>();
			model.User = Membership.GetUserByName(name);

			return View("Profile", model);
		}

		protected void InitEditUserMenu(Core.ViewModels.Menu menu, string tabName)
		{
			foreach (var itemDef in _editMenuDefinition.Items)
			{
				if (string.IsNullOrEmpty(itemDef.PermissionName) ||
					CurrentUserHasPermission(itemDef.PermissionName))
					menu.Items.Add(
						new MenuItem
						{
							Caption = itemDef.Caption,
							Url = UrlHelper.GenerateUrl(null,
								itemDef.ActionName,
								itemDef.ControllerName,
								itemDef.RouteValues,
								RouteTable.Routes,
								Request.RequestContext,
								true),
							Selected = tabName == itemDef.ActionName
						});
			}
		}

	}
}
